import { RoundRectangle } from "./BaseElement.js"
import { setShadow, closeShadow, inRange } from "./utils.js";
import { Car } from "./car.js";

const WALL_WIDTH = 60;
const SPACE_WIDTH = 10;
const PADDING_WIDTH = 10;
const OUTSIDE_SHADOW_WIDTH = 3;

/**
 *
 * @param {HTMLElement} canvas
 */
export function ResizeCanvas( canvas ) {
    canvas.width = WALL_WIDTH * 8 + SPACE_WIDTH * 7 + PADDING_WIDTH * 2 + OUTSIDE_SHADOW_WIDTH;
    canvas.height = WALL_WIDTH * 8 + SPACE_WIDTH * 7 + PADDING_WIDTH * 2 + OUTSIDE_SHADOW_WIDTH;
}

/**
 *
 * @param {HTMLElement} canvas_head
 */
export function ResizeCanvasHead( canvas_head ) {
    canvas_head.style.width = WALL_WIDTH * 8 + SPACE_WIDTH * 7 + PADDING_WIDTH * 2 + OUTSIDE_SHADOW_WIDTH;
}

class Point {
    /**
     *
     * @param {number} x
     * @param {number} y
     */
    constructor( x, y ) {
        this.x = x;
        this.y = y;
    }

    shift( length, direction ) {
        if ( direction === 'left' ) {
            this.x -= length;
        } else if ( direction === 'right' ) {
            this.x += length;
        } else if ( direction === 'up' ) {
            this.y -= length;
        } else if ( direction === 'down' ) {
            this.y += length;
        }
    }

    copy() {
        return { x: this.x, y: this.y };
    }
}

class Wall {
    /**
     *
     * @param {number} width
     * @param {number} space_width
     * @param {number} shadow_width 3 on default
     * @param {number} radius 5 on default
     * @param {string} color 'Lightgrey' on default
     */
    constructor( width, space_width, shadow_width, radius, color ) {
        this.width = width;
        this.space_width = space_width;

        this.shadow_width = shadow_width || 3;
        this.radius = radius || 5;
        this.color = color || 'Lightgrey';
    }

    buildPath( width, space_width ) {
        let points = [];
        let current_point = new Point( 8 * width + 7 * space_width, 0 );
        points.push( current_point.copy() );

        current_point.shift( 3 * width + 2 * space_width, 'down' );
        points.push( current_point.copy() );

        current_point.shift( width, 'left' );
        points.push( current_point.copy() );

        current_point.shift( 2 * width + 2 * space_width, 'up' );
        points.push( current_point.copy() );

        current_point.shift( 6 * width + 7 * space_width, 'left' );
        points.push( current_point.copy() );

        current_point.shift( 6 * width + 7 * space_width, 'down' );
        points.push( current_point.copy() );

        current_point.shift( 6 * width + 7 * space_width, 'right' );
        points.push( current_point.copy() );

        current_point.shift( 3 * width + 3 * space_width, 'up' );
        points.push( current_point.copy() );

        current_point.shift( width, 'right' );
        points.push( current_point.copy() );

        current_point.shift( 4 * width + 3 * space_width, 'down' );
        points.push( current_point.copy() );

        current_point.shift( 8 * width + 7 * space_width, 'left' );
        points.push( current_point.copy() );

        current_point.shift( 8 * width + 7 * space_width, 'up' )
        points.push( current_point.copy() );

        current_point.shift( 8 * width + 7 * space_width, 'right' )
        points.push( current_point.copy() );

        return points;
    }

    drawPath( context ) {
        context.beginPath();

        let points = this.buildPath( this.width, this.space_width );

        context.moveTo( 0 + this.radius, 0 );

        for ( var i = 0; i < points.length - 1; i++ ) {
            context.arcTo( points[ i ].x, points[ i ].y, points[ i + 1 ].x, points[ i + 1 ].y, this.radius );
            // console.log("arc from (" + points[i].x + ',' + points[i].y + ") to (" + points[i + 1].x + ',' + points[i + 1].y + ")");
        }

        context.closePath();
    }

    fill( context ) {
        context.save();
        setShadow( context, this.shadow_width, this.shadow_width, 1 );

        this.drawPath( context );

        context.fillStyle = this.color;
        context.fill();

        closeShadow( context );
        context.restore();
    }
}

export class Map {
    /**
     *
     * @param {number} x map_size in x
     * @param {number} y map_size in y
     * @param {Array<Car>} cars
     */
    constructor() {
        this.size_x = WALL_WIDTH * 8 + SPACE_WIDTH * 7 + PADDING_WIDTH * 2;
        this.size_y = WALL_WIDTH * 8 + SPACE_WIDTH * 7 + PADDING_WIDTH * 2;

        this.MAIN_CAR_INDEX = 0;
        this.car_round_rects = []
        this.gridList = []
        this.build_grid_list()
    }

    /**
     *
     * @param {Array<Car>} cars
     */
    BuildCarRoundRectangles( cars ) {
        let x = 0, y = 0;
        let length = 0;
        let width = 0;
        let car_round_rects = [];

        const origin = new Point( PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH, PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH );

        cars.forEach( car => {
            if ( car.allow_direction === 'x' ) {
                length = WALL_WIDTH * car.length + SPACE_WIDTH * ( car.length - 1 );
                width = WALL_WIDTH;
            } else if ( car.allow_direction === 'y' ) {
                length = WALL_WIDTH;
                width = WALL_WIDTH * car.length + SPACE_WIDTH * ( car.length - 1 );
            }

            x = car.x * ( SPACE_WIDTH + WALL_WIDTH ) + origin.x;
            y = car.y * ( SPACE_WIDTH + WALL_WIDTH ) + origin.y;

            car_round_rects.push( new RoundRectangle( x, y, length, width, 5, car.color ) );
        } );

        this.car_round_rects = car_round_rects;
    }

    build_grid_list() {
        for ( let i = 0; i < 6; i++ ) {
            for ( let j = 0; j < 6; j++ ) {
                let gird = new RoundRectangle( i * ( WALL_WIDTH + SPACE_WIDTH ), j * ( WALL_WIDTH + SPACE_WIDTH ),
                    WALL_WIDTH, WALL_WIDTH, 5, 'Lightgrey' );

                this.gridList.push( gird )
            }
        }
    }

    DrawBackground( context ) {
        let background = new RoundRectangle( 0, 0, this.size_x, this.size_y, 5, 'whiteSmoke' );
        setShadow( context, OUTSIDE_SHADOW_WIDTH, OUTSIDE_SHADOW_WIDTH, 5 );
        background.fill( context );
        closeShadow( context );
    }

    DrawWall( context ) {
        let origin = new Point( PADDING_WIDTH, PADDING_WIDTH );
        let wall = new Wall( WALL_WIDTH, SPACE_WIDTH );

        context.translate( origin.x, origin.y );
        wall.fill( context );
        context.translate( -origin.x, -origin.y );
    }

    DrawGrids( context ) {
        let origin = new Point( PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH, PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH );

        context.translate( origin.x, origin.y );

        setShadow( context, 1, 1, 1 );
        this.gridList.forEach( ( grid ) => {
            grid.fill( context )
        } )
        closeShadow( context );
        context.translate( -origin.x, -origin.y );
    }

    DrawCars( context ) {
        if ( this.car_round_rects.length === 0 ) {
            return
        }

        setShadow( context, 2, 2 );
        this.car_round_rects.forEach( car_round_rect => {
            car_round_rect.fill( context );
        } );
        closeShadow( context );
    }

    /**
     *
     * @param {*} context
     * @param {number} not_drawing_index Index of the car don't want to draw.
     */
    DrawCarsButNotId( context, not_drawing_index ) {
        if ( not_drawing_index === -1 ) {
            this.DrawCars( context );
        }

        setShadow( context, 2, 2 );
        this.car_round_rects.forEach( ( car_round_rect, index ) => {
            if ( index === not_drawing_index ) {
                return;
            }

            car_round_rect.fill( context );
        } );
        closeShadow( context );
    }

    DrawMap( context ) {
        this.DrawBackground( context );
        this.DrawWall( context );
        this.DrawGrids( context );
    }

    MoveCar( mouse, diff, car_index, allow_direction ) {
        // console.log('car_index = ', car_index, diff.x, diff.y);

        let car_round_rect = this.car_round_rects[ car_index ];
        let old_coord_save = {
            x: car_round_rect.x, y: car_round_rect.y
        };

        if ( allow_direction === 'x' ) {
            car_round_rect.x = mouse.x - diff.x;
        } else if ( allow_direction === 'y' ) {
            car_round_rect.y = mouse.y - diff.y;
        }

        if ( !this.validateCar( car_index ) ) {
            car_round_rect.x = old_coord_save.x;
            car_round_rect.y = old_coord_save.y;
        }
    }

    get_touch_on_which_car( device ) {
        if ( this.car_round_rects.length === 0 ) {
            return -1
        }

        let car_index = -1;
        this.car_round_rects.forEach( ( car_round_rect, index ) => {
            if ( car_round_rect.is_touch_in_this( device ) ) {
                car_index = index;
            }
        } );

        return car_index;
    }

    get_touch_on_which_rect = ( device ) => {
        return this.gridList.findIndex( ( rect ) => {
            // So funny...
            return inRange( device.x - WALL_WIDTH, rect.x, rect.x + WALL_WIDTH ) &&
                inRange( device.y - WALL_WIDTH, rect.y, rect.y + WALL_WIDTH )
        } )
    }

    /**
     *
     * @param {number} car_index
     */
    GetAdaptedCar( car_index ) {
        const border_to_wall_width = PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH;
        const division = SPACE_WIDTH + WALL_WIDTH;

        let car_x = 0, car_y = 0;
        let car_round_rect = this.car_round_rects[ car_index ];

        car_x = Math.round( ( car_round_rect.x - border_to_wall_width ) / division );
        car_y = Math.round( ( car_round_rect.y - border_to_wall_width ) / division );

        // console.log("Adaption ", car_index, car_x, car_y);

        return { x: car_x, y: car_y };
    }

    /**
     *
     * @param {number} car_index
     * @param {Car} car
     */
    ResetCar( car_index, car ) {
        let car_round_rect = this.car_round_rects[ car_index ];

        const origin = new Point( PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH, PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH );

        car_round_rect.x = car.x * ( SPACE_WIDTH + WALL_WIDTH ) + origin.x;
        car_round_rect.y = car.y * ( SPACE_WIDTH + WALL_WIDTH ) + origin.y;
    }

    /**
     *
     * @param {number} car_index
     * 碰撞检测，若有碰撞，返回 false
     */
    validateCar( car_index ) {
        let car_round_rect = this.car_round_rects[ car_index ];

        const box_size = 5;
        const border_to_wall_width = PADDING_WIDTH + WALL_WIDTH + SPACE_WIDTH - box_size;
        // check if collide on wall
        if ( car_round_rect.x < border_to_wall_width || car_round_rect.y < border_to_wall_width ||
            ( ( car_index === this.MAIN_CAR_INDEX ) ? ( false ) :
                ( car_round_rect.x + car_round_rect.length >= this.size_x - border_to_wall_width ) ) ||
            car_round_rect.y + car_round_rect.width >= this.size_y - border_to_wall_width ) {
            return false;
        }

        let collided_with_other_car = false;
        this.car_round_rects.forEach( ( other_car_rect, index ) => {
            if ( index === car_index ) {
                return;
            }

            if ( car_round_rect.IsCollideWith( other_car_rect ) ) {
                console.log( car_index, "collide with ", index );
                collided_with_other_car = true;
            }
        } );

        return !collided_with_other_car;
    }

    /**
     *
     * @param {number} car_index
     * @param {Car} car
     * @returns
     */
    IsWin( car_index, car ) {
        return car_index === this.MAIN_CAR_INDEX && ( car.x === 6 || car.x === 7 ) && car.y === 2;
    }
}
